{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# `Nuqleon.Linq.Expressions.Bonsai`\n",
        "\n",
        "Provides a lightweight object model for expression trees with a lightweight representation of reflection."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Reference the library"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Option 1 - Use a local build\n",
        "\n",
        "If you have built the library locally, run the following cell to load the latest build."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "#r \"bin/Debug/net6.0/Nuqleon.Linq.Expressions.Bonsai.dll\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Option 2 - Use NuGet packages\n",
        "\n",
        "If you want to use the latest published package from NuGet, run the following cell."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "#r \"nuget:Nuqleon.Linq.Expressions.Bonsai,*-*\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## (Optional) Attach a debugger\n",
        "\n",
        "If you'd like to step through the source code of the library while running samples, run the following cell, and follow instructions to start a debugger (e.g. Visual Studio). Navigate to the source code of the library to set breakpoints."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "System.Diagnostics.Debugger.Launch();"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# `ExpressionSlim`\r\n",
        "\r\n",
        "`System.Linq.Expressions.ExpressionSlim` is the base type for a lightweight representation of an expression tree, also referred to as a \"Bonsai expression tree\". Its lightweight nature stems from being decoupled from .NET reflection by using `Slim` variants of `Type` and the `MemberInfo` class hierarchy."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Crafting a simple expression tree\r\n",
        "\r\n",
        "To illustrate the manual creation of a Bonsai tree, let's consider the simple expression `1 + 2`. To represent this expression, we need a `BinaryExpression` of type `Add` and two `ConstantExpression`s holding values `1` and `2`. Let's start by crafting these constant nodes first.\r\n",
        "\r\n",
        "Constant expressions consist of a value and a type. Rather than using `System.Object` for the value and `System.Type` for the type, Bonsai expressions abstract over the CLR type system and use alternative representations named `System.ObjectSlim` and `System.TypeSlim`. We'll start by creating a \"simple type\" representing `int`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "System.Int32\r\n"
          },
          "output_type": "unknown"
        }
      ],
      "source": [
        "using System.Reflection;\r\n",
        "\r\n",
        "var asm = new AssemblySlim(typeof(int).Assembly.FullName);\r\n",
        "var int32 = TypeSlim.Simple(asm, typeof(int).FullName);\r\n",
        "\r\n",
        "Console.WriteLine(int32);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Other not-so-simple kinds of types include arrays, references, pointers, and generic types. All of these can be constructed using factory methods on `TypeSlim`. For a simple type, we simply have a name and an assembly.\r\n",
        "\r\n",
        "> **Note:** While we use the notion of an *assembly*, Bonsai trees are decoupled from the CLR type system and can be rebound in other environments. Assemblies just play the role of holders for types and can be implemented in a variety of ways. For example, when binding a Bonsai tree in JavaScript, assemblies may get mapped onto modules."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Next, we can construct two `ObjectSlim` instances using the type constructed above, containing values of `1` and `2`. There are two overloads for the `Create` factory, which we'll explain a bit further on. For now, we'll use the overload that takes an `object` to hold the underlying CLR value, the `TypeSlim` to represent its type, and a `Type` to indicate the CLR type. This may look redundant, but we'll clarify this aspect later."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "1\r\n"
          },
          "output_type": "unknown"
        },
        {
          "data": {
            "text/plain": "2\r\n"
          },
          "output_type": "unknown"
        }
      ],
      "source": [
        "var obj1 = ObjectSlim.Create(1, int32, typeof(int));\r\n",
        "var obj2 = ObjectSlim.Create(2, int32, typeof(int));\r\n",
        "\r\n",
        "Console.WriteLine(obj1);\r\n",
        "Console.WriteLine(obj2);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Now that we have our values, we can start composing a tree on top. To do so, we can use familiar factory methods on `ExpressionSlim`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "1 + 2\r\n"
          },
          "output_type": "unknown"
        }
      ],
      "source": [
        "using System.Linq.Expressions;\r\n",
        "\r\n",
        "var sum = ExpressionSlim.Add(ExpressionSlim.Constant(obj1), ExpressionSlim.Constant(obj2));\r\n",
        "\r\n",
        "Console.WriteLine(sum);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Finally note that slim expression trees as well as slim types do have a `ToCSharpString` which provides a friendly string representation for debugging purposes."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "1 + 2\r\n"
          },
          "output_type": "unknown"
        }
      ],
      "source": [
        "Console.WriteLine(sum.ToCSharpString());"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Crafting a lambda expression tree\r\n",
        "\r\n",
        "To make things a little more interesting, we'll construct a Bonsai lambda expression tree representing `(int x, int y) => x + y`. To do so, we first need to craft a slightly more complex `TypeSlim` that's equivalent to `Func<int, int, int>`, which is a constructed generic type over the `Func<,,>` open generic type."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "System.Func<int, int, int>\r\n"
          },
          "output_type": "unknown"
        }
      ],
      "source": [
        "var func3 = TypeSlim.GenericDefinition(new AssemblySlim(typeof(Func<,,>).Assembly.FullName), typeof(Func<,,>).FullName);\r\n",
        "var func_int_int_int = TypeSlim.Generic(func3, int32, int32, int32);\r\n",
        "\r\n",
        "Console.WriteLine(func_int_int_int.ToCSharpString());"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Next, we need to construct two lambda parameters `x` and `y`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "x\r\n"
          },
          "output_type": "unknown"
        },
        {
          "data": {
            "text/plain": "y\r\n"
          },
          "output_type": "unknown"
        }
      ],
      "source": [
        "var px = ExpressionSlim.Parameter(int32, \"x\");\r\n",
        "var py = ExpressionSlim.Parameter(int32, \"y\");\r\n",
        "\r\n",
        "Console.WriteLine(px.ToCSharpString());\r\n",
        "Console.WriteLine(py.ToCSharpString());"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "And finally, we can construct our lambda expression using `ExpressionSlim.Lambda`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "(x, y) => x + y\r\n"
          },
          "output_type": "unknown"
        }
      ],
      "source": [
        "var f = ExpressionSlim.Lambda(func_int_int_int, ExpressionSlim.Add(px, py), px, py);\r\n",
        "\r\n",
        "Console.WriteLine(f.ToCSharpString());"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Conversion between `Expression` and `ExpressionSlim`\r\n",
        "\r\n",
        "Obviously, hand-crafting these expression trees using factory methods can get quite bewildering. Luckily, the Bonsai expression tree library supports conversions between `Expression` and `ExpressionSlim`, as well as various auxiliary types such as `Type` and `TypeSlim`.\r\n",
        "\r\n",
        "As a first example, we'll convert the `LambdaExpressionSlim` expression we created above to a `LambdaExpression`. This conversion involves binding the slim reflection members to the CLR type system. Under the hood, the string-based representations of types and assemblies are resolved through .NET reflection APIs."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "(x, y) => (x + y)\r\n"
          },
          "output_type": "unknown"
        }
      ],
      "source": [
        "var g = (Expression<Func<int, int, int>>)f.ToExpression();\r\n",
        "\r\n",
        "Console.WriteLine(g);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Note that the resulting expression is fully functional. For example, we can compile and evaluate it."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "3\r\n"
          },
          "output_type": "unknown"
        }
      ],
      "source": [
        "var compiled = g.Compile();\r\n",
        "\r\n",
        "Console.WriteLine(compiled(1, 2));"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Under the hood, the `ToExpression` conversion is using an `ExpressionSlimToExpressionConverter` utility that uses an abstraction over reflection referred to as an \"inverted type space\". The following is an explicit instantiation of this utility, showing all the customization points which can be used to perform binding redirection, caching, etc."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "(x, y) => (x + y)\r\n"
          },
          "output_type": "unknown"
        }
      ],
      "source": [
        "IExpressionFactory factory = ExpressionFactory.Instance;\r\n",
        "IReflectionProvider provider = DefaultReflectionProvider.Instance;\r\n",
        "\r\n",
        "var convertFromSlim = new ExpressionSlimToExpressionConverter(new InvertedTypeSpace(provider), factory);\r\n",
        "\r\n",
        "var g = (Expression<Func<int, int, int>>)convertFromSlim.Visit(f);\r\n",
        "\r\n",
        "Console.WriteLine(g);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "In the opposite direction, we can take a LINQ expression tree of type `Expression` and convert it to `ExpressionSlim`. This is entirely symmetric using a `ToExpression` extension method."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "(x, y) => x + y\r\n"
          },
          "output_type": "unknown"
        }
      ],
      "source": [
        "var h = (LambdaExpressionSlim)g.ToExpressionSlim();\r\n",
        "\r\n",
        "Console.WriteLine(h.ToCSharpString());"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Analogous to the opposite conversion, the `ToExpression` extension method is a convenience method that's based on an `ExpressionToExpressionSlimConverter`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "(x, y) => x + y\r\n"
          },
          "output_type": "unknown"
        }
      ],
      "source": [
        "IExpressionSlimFactory factory = ExpressionSlimFactory.Instance;\r\n",
        "\r\n",
        "var convertToSlim = new ExpressionToExpressionSlimConverter(new TypeSpace(), factory);\r\n",
        "\r\n",
        "var h = (LambdaExpressionSlim)convertToSlim.Visit(g);\r\n",
        "\r\n",
        "Console.WriteLine(h.ToCSharpString());"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Other topics\r\n",
        "\r\n",
        "This section contains a list of topics to be documented in more detail:\r\n",
        "\r\n",
        "* `TypeUnifier` and wildcards.\r\n",
        "* Support for structural types.\r\n",
        "* `Lift` and `Reduce` on `ObjectSlim`.\r\n",
        "* Utilities in `CompilerServices`.\r\n",
        "* Details of `[Inverted]TypeSpace`.\r\n",
        "* Equality comparers, visitors, etc.\r\n",
        "* `IExpressionSerializer`."
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": ".NET (C#)",
      "language": "C#",
      "name": ".net-csharp"
    },
    "language_info": {
      "file_extension": ".cs",
      "mimetype": "text/x-csharp",
      "name": "C#",
      "pygments_lexer": "csharp",
      "version": "9.0"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 4
}